/*
    SEL - Simple DirectMedia Layer Extension Library
    Copyright (C) 2002 Matej Knopp <knopp@users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "tokenizer.h"
#include "resource.h"
#include "../base/debug.h"
#include "../base/error.h"
#include "../base/textstream.h"
#include "../base/misc.h"

namespace SEL
{

ResourceProperty::ResourceProperty ()
{
}

ResourceProperty::ResourceProperty (const std::string &name)
{
	m_name = name;
}

ResourceProperty::ResourceProperty (const std::string &name,
				    const std::string &value)
{
	m_name = name;
	addValue (value);
}				    

void 
ResourceProperty::setValue (const std::string &value, int index)
{
	if (count () == 0)
		addValue (value);
	else
		m_values[index] = value;
}

bool 
ResourceProperty::isInteger (int index) const
{
	const char *str = m_values[index].c_str ();

	if (Tokenizer::isDecInteger (str) || 
	    Tokenizer::isHexInteger (str))
		return true;
	else
		return false;
}

bool 
ResourceProperty::isFloat (int index) const
{
	return Tokenizer::isFloat (m_values[index].c_str ());
}

void 
ResourceProperty::clear ()
{
	m_values.clear ();
}

int 
ResourceProperty::asInteger (int index) const
{
	const char *str = m_values[index].c_str ();
	
	while (Tokenizer::isWhiteSpace (str[0]))
		++str;
	
	if (strlen (str) >= 2)
		if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
		{
			int i;
			sscanf (str, "%x", &i);
			return i;
		}
	return atoi (str);	
}
	
float 
ResourceProperty::asFloat (int index) const
{
	return atof (m_values[index].c_str ());
}


ResourceProperty &
ResourceProperties::get (const std::string & name)
{
	std::vector<ResourceProperty>::iterator it = 
		m_properties.begin ();
	
	while (it != m_properties.end ()) 
	{
		if (it->name () == name)
			return *it;
		++it;
	}

	warning ("Requested property `" << name << "' does not exist!");
	throw Error ("Couldn't find requested property!");
}

const ResourceProperty &
ResourceProperties::get (const std::string & name) const
{
	std::vector<ResourceProperty>::const_iterator it = 
		m_properties.begin ();
	
	while (it != m_properties.end ()) 
	{
		if (it->name () == name)
			return *it;
		++it;
	}

	warning ("Requested property `" << name << "' does not exist!");
	throw Error ("Couldn't find requested property!");
}

bool
ResourceProperties::exists (const std::string &name) const
{
	std::vector<ResourceProperty>::const_iterator it = m_properties.begin ();
	
	while (it != m_properties.end ()) 
	{
		if (it->name () == name)
			return true;
		++it;
	}

	return false;
}
	
void 
ResourceProperties::add (const ResourceProperty &property)
{
	m_properties.push_back (property);
}

Resource::Resource (const std::string &name,
		    const std::string &location,
		    const ResourceProperties &properties)
{
	m_type = "default";
	m_name = name;
	m_location = location;
	m_properties = properties;
}

Resource::~Resource ()
{
}

void 
Resource::setType (const std::string &type)
{
	m_type = type;
}

void 
Resource::setName (const std::string &name)
{
	m_name = name;
}

void
Resource::setLocation (const std::string &location)
{
	m_location = location;
}

void
Resource::load ()
{
	assert (m_manager);
	if (!m_manager || !hasData ())
		return; // grrr...
	
	RWops *ops = m_manager->createResourceRWops (*this);
	try 
	{
		load (*ops);
	}
	catch (...)
	{
		del (ops);
		throw;
	}
	del (ops);
}

void 
Resource::copyData (RWops &src, RWops &dst)
{
	char buffer[4096];

	// save the resource size;
	dst << (Uint32)	src.seek (0, SEEK_END);
	src.seek (0, SEEK_SET); // go back
			
	// copy the file
	while (1)
	{
		unsigned int nread = src.read (buffer, 1, sizeof (buffer));
		dst.write (buffer, 1, nread);
		if (nread < sizeof (buffer))
			break;
	}
}

ResourceManager::ResourceManager (const std::string & filename, 
				  bool is_datafile)
{
	m_datafile = is_datafile;
	m_filename = filename;
	loadResources ();
	m_tokenizer = NULL;
}				  

ResourceManager::~ResourceManager ()
{
	if (m_tokenizer) 
	{
		warning ("Tokenizer was alive (?!)");
		del (m_tokenizer);
	}
	freeResources ();
}

void 
ResourceManager::parseError (const std::string & error)
{
	TextStream msg;
	msg << "Error while parsing file `" << m_tokenizer->filename () 
	<< "' on line " << 
	(m_tokenizer ?  m_tokenizer->lastLine () + 1 : -1 ) << ":\n  " << error;
	// the error is fatal so delete the tokenizer
	del (m_tokenizer);
	throw (Error (msg ()));
}

Resource *
ResourceManager::addResource (const std::string &type,
			      const std::string &name,
			      const std::string &location,
			      const ResourceProperties &properties)
{
	Resource *res = ResourceType::createResource (type, name, 
						      location, properties);

	res->m_manager = this;
	m_resources.push_back (res);
	return res;
}


void 
ResourceManager::pushPrefix (const std::string &_prefix, bool append)
{
	std::string prefix = _prefix;
	if (prefix[prefix.length()-1] != '/')
		prefix += '/';
	if (m_prefixes.empty () || append == false)
		m_prefixes.push (prefix);
	else
		m_prefixes.push (m_prefixes.top () + prefix);
}
	
void 
ResourceManager::popPrefix ()
{
	m_prefixes.pop ();
}
	
void 
ResourceManager::clearPrefix ()
{
	while (!m_prefixes.empty ())
		m_prefixes.pop ();
}
	
Resource *
ResourceManager::get (const std::string &_name, bool no_exception)
{	
	Resource *res = NULL;
	std::string name;
	if (m_prefixes.empty ())
		name = _name;
	else
		name = m_prefixes.top () + _name;		
	
	std::list<Resource*>::iterator it = m_resources.begin ();
	while (it != m_resources.end ())
	{
		if ((*it)->name () == name)
		{
			res = (*it);
			break;
		}
		++it;
	}
	if (!res && !no_exception)
	{
		TextStream stream;
		stream << "Requested resource `" << name << "' does not exist!";
		throw Error (stream ());
	}
	return res;
}

const Resource *
ResourceManager::get (const std::string &_name, bool no_exception) const
{	
	const Resource *res = NULL;
	std::string name;
	if (m_prefixes.empty ())
		name = _name;
	else
		name = m_prefixes.top () + _name;		
	
	std::list<Resource*>::const_iterator it = m_resources.begin ();
	while (it != m_resources.end ())
	{
		if ((*it)->name () == name)
		{
			res = (*it);
			break;
		}
		++it;
	}
	if (!res && !no_exception)
	{
		TextStream stream;
		stream << "Requested resource `" << name << "' does not exist!";
		throw Error (stream ());
	}
	return res;
}


#define T_OPERATOR   Tokenizer::TOKEN_OPERATOR
#define T_STRING     Tokenizer::TOKEN_STRING
#define _TYPE        m_tokenizer->tokenType 
#define _VALUE       m_tokenizer->tokenValue 
#define _NEXT_TYPE   m_tokenizer->nextTokenType
#define _NEXT_VALUE  m_tokenizer->nextTokenValue
#define _NEXT()	     if (!m_tokenizer->nextToken ()) goto finish; 
#define _PEEK_NEXT() if (!m_tokenizer->peekNextToken ()) goto finish;

void 
ResourceManager::loadFromScript (RWops *ops, const std::string &filename)
{

	std::stack<std::string> sections;
	m_tokenizer = new Tokenizer (ops, filename);
	
	bool ok;
	while (1)
	{
		ok = sections.empty ();
		_NEXT ();
		ok = false;

		if (_TYPE () == T_OPERATOR && sections.empty ())
			parseError ("Unexpected operator (expected string) !");

		if (_TYPE () == T_OPERATOR)
		{
			if (_VALUE () != "}")
				parseError ("Unexpected operator (expected '}') !");
			else
			{				
				debugI ("RES: Closed section: " << sections.top ());
				sections.pop ();
				continue;
			}
		}
		
		_PEEK_NEXT ();
		if (_NEXT_TYPE () == T_STRING) // include or section
		{		
			if (_VALUE () == "include")
			{
				_NEXT ();
				FileRWops *ops = NULL;
				try 
				{
					ops = new FileRWops (_VALUE (), "rb");
				}
				catch (Error error)
				{
					TextStream stream;
					stream << "Couldn't include file: "
					<< _VALUE ();
					parseError (stream ());
				}
				if (!m_tokenizer->pushFile (ops, _VALUE ()))
				{
					del (ops);
					TextStream stream;
					stream << "Error including file: "
					<< _VALUE () << " - loop detected.";
					parseError (stream ());
				}
				debugI ("RES: Including file: " << _VALUE ());
				continue;
			}
			else if (_VALUE () == "section")
			{
				_NEXT ();
				std::string sec_name = _VALUE ();
				_NEXT ();
				if (_TYPE () != T_OPERATOR ||
				    _VALUE () != "{")
					parseError ("`{' expected after the"
						    " section name.");
				if (sections.empty ())
					sections.push (sec_name + "/");
				else
					sections.push (sections.top () + 
						       sec_name + "/");	
				debugI ("RES: Opened section: " 
					 << sections.top ());
				continue;						       
			}
			else
				parseError ("`=' expected after the resource"
					    " name.");
		} // if (_NEXT_TYPE () == T_STRING) // include or section		

		if (_NEXT_VALUE () != "=")
			parseError ("`=' expected after the resource name.");

		std::string name = _VALUE ();
		if (!sections.empty ())
			name = sections.top () + name;

		_NEXT (); _NEXT ();
		if (_TYPE () != T_STRING)
			parseError ("Resource location after `=' expected.");
		std::string location = _VALUE ();
		
		std::string type = "default";
		_PEEK_NEXT ();
		
		if (_NEXT_TYPE () == T_OPERATOR && _NEXT_VALUE () == "(")
		{
			_NEXT (); _NEXT ();
			if (_TYPE () != T_STRING)
				parseError ("Resource type after `(' expected.");
			type = _VALUE ();
			_NEXT ();
			if (_TYPE () != T_OPERATOR || _VALUE () != ")")
			{
				parseError ("`)' is expected after the resource"
					    " type.");
			}
			_PEEK_NEXT ();
		}
		
		ResourceProperties props;
		if (_NEXT_TYPE () == T_OPERATOR && _NEXT_VALUE () == "{")
		{
			_NEXT ();
			// have properties specified
			while (1)
			{
				_NEXT ();
				if (_TYPE () == T_OPERATOR)
				{
					if (_VALUE () == "}")
						break;
					else
						parseError ("Expected string "
							    "or `}'.");
				}
				// so it's a string
				ResourceProperty prop;
				prop.setName (_VALUE ());
				_NEXT ();
				if (_TYPE () != T_OPERATOR || _VALUE () != "=")
				{
					parseError ("Expected `=' followed by "
						    "optionally comma separated"
						    " values ending with `;' !");
				}
				
				// get the values
				while (1)
				{
					_NEXT ();
					if (_TYPE () == T_OPERATOR)
					{
						if (_VALUE () == ";")
							break;
						else if (_VALUE () == ",")
							continue;
						else
							parseError 
								("Unexpected"
								" operator (`,'"
								" or `;' "
								"expected) !");
					}
					prop.addValue (_VALUE ());
				}
				props.add (prop);
			}
		}
		debugI ("RES: Have resource `" << name << "' with location `"
			<< location << "' of type `" << type << "'");
		addResource (type, name, location, props);
	}
finish:
	if (!ok || !sections.empty ())
		parseError ("Unexpected end of file");

	del (m_tokenizer);
	
}

#undef T_OPERATOR
#undef T_STRING
#undef _TYPE
#undef _VALUE
#undef _NEXT_TYPE
#undef _NEXT_VALUE
#undef _NEXT
#undef _PEEK_NEXT

void 
ResourceManager::loadResources ()
{
	FileRWops *ops = new FileRWops (m_filename, "rb");
	if (m_datafile)
		loadFromDataFile (ops);
	else
		loadFromScript (ops, m_filename);
}

void 
ResourceManager::freeResources ()
{
	std::list<Resource*>::iterator it = m_resources.begin ();
	while (it != m_resources.end ())
	{
		delete (*it);
		++it;
	}	
	m_resources.clear ();
}


RWops *
ResourceManager::createResourceRWops (const Resource &res) const
{
	if (m_datafile)
	{
		FileRWops *ops = new FileRWops (m_filename, "rb");
		ops->seek (m_header_size + res.m_data_offset, SEEK_SET);
		Uint32 size;
		*ops >> size;
		ops->setVirtualBegin (ops->tell ());
		ops->setVirtualSize (size);
		return ops;
	}
	else
	{
		FileRWops *ops = new FileRWops (res.location (), "rb");
		return ops;
	}
}

const Uint32 header[2] = {0x3A4C4553, 0x5345523A}; // SEL::RES
const Uint16 version_hi = 1;
const Uint16 version_lo = 0;

void 
ResourceManager::saveDataFile (RWops *ops) const
{
	Uint32 data_offset = 0;

	// save header 8b
	*ops << header[0];
	*ops << header[1];

	// save version 4b
	*ops << version_hi;
	*ops << version_lo;
	
	// the number of resources 4b
	*ops << (Uint32) m_resources.size ();
	
	// save the resources
	std::list<Resource*>::const_iterator it = m_resources.begin ();
	while (it != m_resources.end ())
	{
		Resource *res = (*it);
		// save the resource name, location, type
		*ops << res->name ();
		*ops << res->location ();
		*ops << res->type ();
		
		// save the number of properties
		*ops << (Uint32) res->properties ().count ();
		
		if (res->properties ().count () != 0)
		{
			// save the properties
			std::vector<ResourceProperty>::const_iterator it =
				res->properties ().properties ().begin ();
			while (it != res->properties ().properties ().end ())
			{
				*ops << it->name ();
				*ops << (Uint32) it->count ();

				// save the property values
				std::vector<std::string>::const_iterator vit =
					it->values ().begin ();
				while (vit != it->values ().end ())
				{
					*ops << (*vit);
					++vit;
				}
				++it;
			}
		}
		++it;
	}

	// save the data
	it = m_resources.begin ();
	
	while (it != m_resources.end ())
	{
		Resource *res = (*it);
		if (res->hasData ())
		{
			FileRWops data (res->location (), "rb");
			// copy
			res->copyData (data, *ops);
		}
		++it;
	}
	
}

void 
ResourceManager::createDataFile (const std::string & filename) const
{
	FileRWops *ops = new FileRWops (filename, "wb");
	try
	{
		saveDataFile (ops);
	}
	catch (...)
	{
		del (ops);
		throw;
	}
	del (ops);
}

#define _CHECK_RW__OK() \
	if (ops->status () != RWops::STAT_OK) \
	{ \
		del (ops); \
		return; \
	}

#define _CHECK_RW__ERR() \
	if (ops->status () != RWops::STAT_OK) \
	{ \
		del (ops); \
		throw Error ("Unexpected end of data file."); \
	}	

void 
ResourceManager::loadFromDataFile (RWops *ops)
{
	// read and check the header
	Uint32 _header[2];

	*ops >> _header[0];
	*ops >> _header[1];
	if (header[0] != _header[0] || header[1] != _header[1])
	{
		del (ops);
		throw Error ("Invalid data file header!");
	}

	_CHECK_RW__ERR ();

	// check the version
	Uint16 _version_hi;
	Uint16 _version_lo;
	*ops >> _version_hi;
	*ops >> _version_lo;
	if ( (_version_hi > version_hi) || 
	     (_version_hi == version_hi && _version_lo > version_lo) )
	{
		del (ops);
		throw Error ("Unknown data file version!");
	}
	
	_CHECK_RW__OK();
	
	// the number of resources
	Uint32 num_res;
	*ops >> num_res;

	for (Uint32 i=0; i<num_res; i++)
	{
		_CHECK_RW__ERR ();
		// get the name, location, type
		std::string name;
		std::string location;
		std::string type;
		*ops >> name;
		*ops >> location;
		*ops >> type;

		// load the properties
		ResourceProperties props;
		Uint32 num_props;
		*ops >> num_props;

		for (Uint32 ii=0; ii<num_props; ii++)
		{
			ResourceProperty prop;

			// get the property name
			std::string prop_name;
			*ops >> prop_name;
			prop.setName (prop_name);

			// get the number of values;
			Uint32 num_vals;
			*ops >> num_vals;

			// get the values
			for (Uint32 iii=0; iii<num_vals; iii++)
			{
				std::string value;
				*ops >> value;
				prop.addValue (value);
			}
			props.add (prop);
		}
		
		Resource *res = addResource (type, name, location, props);
	}
	m_header_size = ops->tell ();

	// Get the data offsets
	Uint32 data_offset = 0;
	std::list<Resource*>::iterator it = m_resources.begin ();
	while (it != m_resources.end ())
	{
		Resource *res = (*it);
		if (res->hasData ())
		{
			res->m_data_offset = data_offset;
			Uint32 size;
			*ops >> size;
			_CHECK_RW__ERR ();
			ops->seek (size, SEEK_CUR);
			data_offset += size + 4;
		}
		++it;
	}

	_CHECK_RW__OK ();
	del (ops);
}

#undef _CHECK_RW__ERR
#undef _CHECK_RW__OK

void 
ResourceManager::loadAll ()
{
	std::list<Resource*>::iterator it = m_resources.begin ();
	while (it != m_resources.end ())
	{
		if ((*it)->hasData () && !(*it)->loaded ())
			(*it)->load ();
		++it;
	}
}
	
void 
ResourceManager::freeAll ()
{
	std::list<Resource*>::iterator it = m_resources.begin ();
	while (it != m_resources.end ())
	{
		if ((*it)->loaded () )
			(*it)->free ();
		++it;
	}
}
	
void 
ResourceManager::loadSection (const std::string & _section, bool use_prefix)
{
	std::string section;
	if (use_prefix && !m_prefixes.empty ())
		section = m_prefixes.top () + _section;
	else
		section = _section;

	if (section[section.length()-1] != '/')
		section += '/';
	
	std::list<Resource*>::iterator it = m_resources.begin ();
	while (it != m_resources.end ())
	{
		const std::string & name = (*it)->name ();

		if ((*it)->hasData () && !(*it)->loaded () &&
		    (name.compare (0, section.length (), section) == 0))
		{
			(*it)->load ();
		}

		++it;
	}
}
	
void 
ResourceManager::loadSection ()
{
	if (m_prefixes.empty ())
		loadAll ();
	else
		loadSection (m_prefixes.top (), false);
}
	
void 
ResourceManager::freeSection (const std::string & _section, 
			      bool use_prefix)
{
	std::string section;
	if (use_prefix && !m_prefixes.empty ())
		section = m_prefixes.top () + _section;
	else
		section = _section;

	if (section[section.length()-1] != '/')
		section += '/';
	
	std::list<Resource*>::iterator it = m_resources.begin ();
	while (it != m_resources.end ())
	{
		const std::string & name = (*it)->name ();

		if ((*it)->loaded () &&
		    (name.compare (0, section.length (), section) == 0))		    
		{
			(*it)->free ();
		}

		++it;
	}
}
	
void 
ResourceManager::freeSection ()
{
	if (m_prefixes.empty ())
		freeAll ();
	else
		freeSection (m_prefixes.top (), false);
}

//! TODO - protect this with a mutex ?!
std::list <ResourceType*> ResourceType::m_types;

ResourceType::ResourceType (const std::string &type)
{
	m_type = type;
	m_types.push_back (this);
}

ResourceType::~ResourceType ()
{
	m_types.remove (this);
}

Resource *
ResourceType::createResource (const std::string &stype,
			      const std::string &name,
			      const std::string &location,
			      const ResourceProperties &properties)
{
	ResourceType *type = NULL;
	std::list<ResourceType*>::iterator it = m_types.begin ();
	while (it != m_types.end ())
	{
		if ((*it)->type () == stype)
			type = (*it);
		++it;
	}
	if (!type) 
	{
		warning ("Couldn't find requested resource type `"<<stype<<"' !");
		throw Error ("Couldn't create requested resource!");
	}
	Resource *res = type->createResource (name, location, properties);
	if (res)
	{
		if (res->type () != stype)
			warning ("Created resource type and requested "
				 << "resource type does not match." << std::endl
				 << "    requested: `"<<stype<<"', created: `"
				 << res->type () << "'" << std::endl);
	}
	return res;
}			      


} // namespace SEL
